<?php
/**
 * a oauth client
 *
 * @author tom.wang<tom.wang.sz@qq.com>
 */
class FrameworkOAuthClient {
	protected $cfg;
	protected $sig_method;
	protected $consumer;
	
	/**
	 * construct
	 *
	 * @param array $cfg
	 */
	public function __construct($cfg = array()) {
		$this->setCfg($cfg);
		$this->initial();
	}
	
	/**
	 * get request token(unauthorize)
	 *
	 * @return[oauth_token] string
	 * @return[oauth_token_secret] string
	 */
	public function getRequestToken($addKeys = array()) {
		$url = $this->cfg['request_token_url'];
		foreach($addKeys as $key => $val) {
			$url .= (false !== strpos($url, '?') ? '&' : '?');
			$url .= $key . '=' . urlencode($val);
		}
		$parsed = parse_url($url);
		$params = array();
		isset($parsed['query']) && parse_str($parsed['query'], $params);
		
		$req_req = OAuthRequest::from_consumer_and_token($this->consumer, NULL, "GET", $url, $params);
		$req_req->sign_request($this->sig_method, $this->consumer, NULL);
		
		$response = $this->http($req_req . '', 'GET');
		$token = OAuthUtil::parse_parameters($response);
		
		return $token;
	}
	
	/**
	 * get authorize url
	 *
	 * @param string $request_token_key
	 * @param array $addKeys
	 * @return string
	 */
	public function getAuthorizeUrl($request_token_key, $addKeys = array()) {
		if(empty($request_token_key)) throw new OAuthException('Request token needed!');
		
		$parsed = parse_url($this->cfg['authorize_url']);
		$params = array();
		isset($parsed['query']) && parse_str($parsed['query'], $params);
		
		$auth_url = $this->cfg['authorize_url'] . (!empty($params) ? '&' : '?') . "oauth_token={$request_token_key}&oauth_callback=".urlencode($this->cfg['authorize_callback']);
		
		foreach($addKeys as $key => $val) {
			$auth_url .= "&{$key}=" . urlencode($val);
		}
		
		return $auth_url;
	}
	
	/**
	 * get access token
	 *
	 * @param string $request_token_key
	 * @param string $request_token_secret
	 * 
	 * @return[oauth_token] string
	 * @return[oauth_token_secret] string
	 */
	public function getAccessToken($request_token_key, $request_token_secret, $addKeys = array()) {
		if(empty($request_token_key) || empty($request_token_secret)) throw new OAuthException('Both Request token and Request token secret needed!');
		$token = new OAuthToken($request_token_key, $request_token_secret);
		$url = $this->cfg['access_token_url'];
		foreach($addKeys as $key => $val) {
			$url .= (false !== strpos($url, '?') ? '&' : '?');
			$url .= $key . '=' . urlencode($val);
		}
		$parsed = parse_url($url);
		$params = array();
		isset($parsed['query']) && parse_str($parsed['query'], $params);
		
		$acc_req = OAuthRequest::from_consumer_and_token($this->consumer, $token, "GET", $url, $params);
		$acc_req->sign_request($this->sig_method, $this->consumer, $token);

		$response = $this->http($acc_req . '', 'GET');
		$token = OAuthUtil::parse_parameters($response);
		
		return $token;
	}
	
	/**
	 * invoke a api
	 *
	 * @param array $postfields
	 */
	public function api($access_token_key, $access_token_secret, $postfields = array()) {
		if(empty($access_token_key) || empty($access_token_secret)) throw new OAuthException('Both Access token and Access token secret needed!');
		$token = new OAuthToken($access_token_key, $access_token_secret);
		$parsed = parse_url($this->cfg['api_url']);
		$params = array();
		isset($parsed['query']) && parse_str($parsed['query'], $params);
		
		//add format param
		$postfields['format'] = $this->cfg['format'];
		
		//$postfields must used to signature together
		$params = array_merge($params, $postfields);
		$api_req = OAuthRequest::from_consumer_and_token($this->consumer, $token, "POST", $this->cfg['api_url'], $params);
		$api_req->sign_request($this->sig_method, $this->consumer, $token);
		
		$response = $this->http($api_req . '', 'POST', $params);
		
		return $response;
	}
	
	/**
	 * set config
	 *
	 * @param array $cfg
	 */
	protected function setCfg($cfg) {
		$this->cfg = require FRAMEWORK_OAUTH_ROOT . 'Client/config.php';
		foreach($cfg as $key => $val) {
			if(array_key_exists($key, $this->cfg)) $this->cfg[$key] = $val;
		}
	}
	
	/**
	 * initialize
	 *
	 */
	protected function initial() {
		$this->sig_method = new OAuthSignatureMethod_HMAC_SHA1();
		$this->consumer = new OAuthConsumer($this->cfg['consumer_key'], $this->cfg['consumer_secret'], NULL);
	}
	
	/**
	 * gzip decode
	 */
	private function gzdecode($data,&$filename='',&$error='',$maxlength=null) {
		$len = strlen($data);
		if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) {
			$error = "Not in GZIP format.";
			return null;  // Not GZIP format (See RFC 1952)
		}
		$method = ord(substr($data,2,1));  // Compression method
		$flags  = ord(substr($data,3,1));  // Flags
		if ($flags & 31 != $flags) {
			$error = "Reserved bits not allowed.";
			return null;
		}
		// NOTE: $mtime may be negative (PHP integer limitations)
		$mtime = unpack("V", substr($data,4,4));
		$mtime = $mtime[1];
		$xfl   = substr($data,8,1);
		$os    = substr($data,8,1);
		$headerlen = 10;
		$extralen  = 0;
		$extra     = "";
		if ($flags & 4) {
			// 2-byte length prefixed EXTRA data in header
			if ($len - $headerlen - 2 < 8) {
				return false;  // invalid
			}
			$extralen = unpack("v",substr($data,8,2));
			$extralen = $extralen[1];
			if ($len - $headerlen - 2 - $extralen < 8) {
				return false;  // invalid
			}
			$extra = substr($data,10,$extralen);
			$headerlen += 2 + $extralen;
		}
		$filenamelen = 0;
		$filename = "";
		if ($flags & 8) {
			// C-style string
			if ($len - $headerlen - 1 < 8) {
				return false; // invalid
			}
			$filenamelen = strpos(substr($data,$headerlen),chr(0));
			if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
				return false; // invalid
			}
			$filename = substr($data,$headerlen,$filenamelen);
			$headerlen += $filenamelen + 1;
		}
		$commentlen = 0;
		$comment = "";
		if ($flags & 16) {
			// C-style string COMMENT data in header
			if ($len - $headerlen - 1 < 8) {
				return false;    // invalid
			}
			$commentlen = strpos(substr($data,$headerlen),chr(0));
			if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
				return false;    // Invalid header format
			}
			$comment = substr($data,$headerlen,$commentlen);
			$headerlen += $commentlen + 1;
		}
		$headercrc = "";
		if ($flags & 2) {
			// 2-bytes (lowest order) of CRC32 on header present
			if ($len - $headerlen - 2 < 8) {
				return false;    // invalid
			}
			$calccrc = crc32(substr($data,0,$headerlen)) & 0xffff;
			$headercrc = unpack("v", substr($data,$headerlen,2));
			$headercrc = $headercrc[1];
			if ($headercrc != $calccrc) {
				$error = "Header checksum failed.";
				return false;    // Bad header CRC
			}
			$headerlen += 2;
		}
		// GZIP FOOTER
		$datacrc = unpack("V",substr($data,-8,4));
		$datacrc = sprintf('%u',$datacrc[1] & 0xFFFFFFFF);
		$isize = unpack("V",substr($data,-4));
		$isize = $isize[1];
		// decompression:
		$bodylen = $len-$headerlen-8;
		if ($bodylen < 1) {
			// IMPLEMENTATION BUG!
			return null;
		}
		$body = substr($data,$headerlen,$bodylen);
		$data = "";
		if ($bodylen > 0) {
			switch ($method) {
			case 8:
				// Currently the only supported compression method:
				$data = gzinflate($body,$maxlength);
				break;
			default:
				$error = "Unknown compression method.";
				return false;
			}
		}  // zero-byte body content is allowed
		// Verifiy CRC32
		$crc   = sprintf("%u",crc32($data));
		$crcOK = $crc == $datacrc;
		$lenOK = $isize == strlen($data);
		if (!$lenOK || !$crcOK) {
			$error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.');
			return false;
		}
		return $data;
	}
	
	/**
	 * normal http request
	 */
	protected function http($url, $method, $postfields = array(), $multi = false, $gzip = false){
		$urlArr = parse_url($url);
		//check https
		$version = '1.1';
		$host = $headerHost = isset($urlArr['host']) ? $urlArr['host'] : $this->cfg['host'];
		if('https' === strtolower($urlArr['scheme'])) {
			$port = (isset($urlArr['port']) && '443' !== $urlArr['port']) ? $urlArr['port'] : '443';
			$host = "ssl://{$host}";
		} else {
			$port = (isset($urlArr['port']) && '80' !== $urlArr['port']) ? $urlArr['port'] : '80';
		}
		
		$requestPath = $urlArr['path'];
		if(isset($urlArr['query'])) $requestPath .= "?{$urlArr['query']}";

		$header = "{$method} {$requestPath} HTTP/{$version}\r\n";
		$header .= "Host: {$headerHost}\r\n";
		$gzip && $header .= "Accept-Encoding: gzip\r\n";
		if('post' === strtolower($method)) {
			//handle $postfields
			$postfieldsStr = '';
			if($multi) {
				$boundary = "---------------------------".substr(md5(rand(0,32000)),0,10);
				$header .= "Content-Type: multipart/form-data; boundary={$boundary}\r\n";
				foreach($postfields as $key => $val) {
					$postfieldsStr .= "--{$boundary}\r\n";
					if(is_file($val)) {
						$postfieldsStr .= "Content-Disposition: form-data; name=\"{$key}\"; filename=\"" . basename($val) . "\"\r\n";
						$postfieldsStr .= "Content-Type: " . $this->getMimeType($val) . "\r\n\r\n";
						$postfieldsStr .= file_get_contents($val) . "\r\n";
					} else {
						$postfieldsStr .= "Content-Disposition: form-data; name=\"{$key}\"\r\n\r\n";
						$postfieldsStr .= "{$val}\r\n";
					}
				}
				
				$postfieldsStr .= "--{$boundary}--\r\n";
			} else {
				$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
				foreach($postfields as $key => $val) {
					$postfieldsStr .= urlencode($key) . '=' . urlencode($val) . '&';
				}
				$postfieldsStr = substr($postfieldsStr, 0, -1);
			}
			
			$header .= "Content-Length: ".strlen($postfieldsStr)."\r\n";
			$header .= "Connection: Close\r\n\r\n";
			$header .= $postfieldsStr;
		} else {
			$header .= "Connection: Close\r\n\r\n";
		}

		$ret = '';
		
		$fp = @fsockopen($host,$port,$errno,$errstr,30);

		if(!$fp) {
			throw new OAuthException('open socket fail');
		} else {
			fwrite($fp, $header);
			while(!feof($fp)) {
				$ret .= fgets($fp, 4096);
			}
			fclose($fp);
			if(false !== strrpos($ret,'Transfer-Encoding: chunked')){
				$info = explode("\r\n\r\n",$ret);
				$response = explode("\r\n",$info[1]);
				$t = array_slice($response,1,-1);

				$returnInfo = implode('',$t);
			}else{
				$response = explode("\r\n\r\n",$ret);
				$returnInfo = $response[1];
			}
			
			$gzip && $returnInfo = $this->gzdecode($returnInfo);
			return $returnInfo;
		}
	}
	
	/**
	 * get a file's mime
	 *
	 * http://cn.php.net/manual/en/function.mime-content-type.php
	 */
	protected function getMimeType($filename) {
		$mime_types = array(
			'txt' => 'text/plain',
			'htm' => 'text/html',
			'html' => 'text/html',
			'php' => 'text/html',
			'css' => 'text/css',
			'js' => 'application/javascript',
			'json' => 'application/json',
			'xml' => 'application/xml',
			'swf' => 'application/x-shockwave-flash',
			'flv' => 'video/x-flv',
			
			// images
			'png' => 'image/png',
			'jpe' => 'image/jpeg',
			'jpeg' => 'image/jpeg',
			'jpg' => 'image/jpeg',
			'gif' => 'image/gif',
			'bmp' => 'image/bmp',
			'ico' => 'image/vnd.microsoft.icon',
			'tiff' => 'image/tiff',
			'tif' => 'image/tiff',
			'svg' => 'image/svg+xml',
			'svgz' => 'image/svg+xml',
			
			// archives
			'zip' => 'application/zip',
			'rar' => 'application/x-rar-compressed',
			'exe' => 'application/x-msdownload',
			'msi' => 'application/x-msdownload',
			'cab' => 'application/vnd.ms-cab-compressed',
			
			// audio/video
			'mp3' => 'audio/mpeg',
			'qt' => 'video/quicktime',
			'mov' => 'video/quicktime',
			
			// adobe
			'pdf' => 'application/pdf',
			'psd' => 'image/vnd.adobe.photoshop',
			'ai' => 'application/postscript',
			'eps' => 'application/postscript',
			'ps' => 'application/postscript',
			
			// ms office
			'doc' => 'application/msword',
			'rtf' => 'application/rtf',
			'xls' => 'application/vnd.ms-excel',
			'ppt' => 'application/vnd.ms-powerpoint',
			
			// open office
			'odt' => 'application/vnd.oasis.opendocument.text',
			'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
		);
		
		$ext = strtolower(array_pop(explode('.',$filename)));
		if (array_key_exists($ext, $mime_types)) {
			return $mime_types[$ext];
		} elseif (function_exists('finfo_open')) {
			$finfo = finfo_open(FILEINFO_MIME);
			$mimetype = finfo_file($finfo, $filename);
			finfo_close($finfo);
			return $mimetype;
		} else {
			return 'application/octet-stream';
		}
	}
 
	/**
	 * curl http request
	 */
	/*protected function http($url, $method, $postfields = array() , $multi = false){
        $ci = curl_init();
        curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader'));
        curl_setopt($ci, CURLOPT_HEADER, FALSE);

        switch ($method) {
        case 'POST':
            curl_setopt($ci, CURLOPT_POST, TRUE);
            if (!empty($postfields)) {
                curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
            }
            break;
        case 'DELETE':
            curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');
            if (!empty($postfields)) {
                $url = "{$url}?{$postfields}";
            }
        }

        $header_array = array();
        $header_array2=array();
        if( $multi )
        	$header_array2 = array("Content-Type: multipart/form-data; boundary=" . OAuthUtil::$boundary , "Expect: ");
        foreach($header_array as $k => $v)
            array_push($header_array2,$k.': '.$v);

        curl_setopt($ci, CURLOPT_HTTPHEADER, $header_array2 );
        curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE );
        curl_setopt($ci, CURLOPT_URL, $url);

        $response = curl_exec($ci);
        curl_close($ci);
        return $response;
	}
	
	protected function getHeader($ch, $header) {
        $i = strpos($header, ':');
        if (!empty($i)) {
            $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
            $value = trim(substr($header, $i + 2));
        }
        return strlen($header);
	}*/
}
?>